//
// Created by ktsq on 2021/6/20.
//

#include "test.h"

#include "ObjLoader.h"

#include <KPBR/KPBR.h>
#include <kgl.h>

#include <glad/glad.h>
#include <GLFW/glfw3.h>

#include <stb_image.h>
#include <stb_image_write.h>

#include <iostream>

std::string GetFileName(const std::string &name, const std::string &extend);

kp::Ref<kp::ImageFilm> pbr(const std::string &scene_name, size_t w, size_t h, kp::IntegrateState &state);

int ch1_primary_main() {
    kgl::Log::Init();
    bool isRunning = false;
    kp::Vec2i winSize(800, 600);
    auto win = kgl::MakeScope<kgl::Window>(kgl::Window(
            kgl::WindowProps("KGL Engine", winSize.w, winSize.h, true, false)
    ));
    win->addEventCallback([&](kgl::Event &ev) {
        kgl::EventDispatcher dispatcher(ev);
        dispatcher.Dispatch<kgl::WindowCloseEvent>([&](kgl::WindowCloseEvent &ev) {
            isRunning = false;
            return false;
        });
        dispatcher.Dispatch<kgl::WindowResizeEvent>([](kgl::WindowResizeEvent &ev) {
            glViewport(0, 0, ev.width, ev.height);
            return false;
        });
        dispatcher.Dispatch<kgl::KeyPressedEvent>([&](kgl::KeyPressedEvent &ev) {
            if (ev.keyCode == kgl::Key::Escape)
                isRunning = false;
            return false;
        });
    });
    isRunning = true;

    // create shader
    auto shader = kgl::MakeRef<kgl::Shader>("../kgl-data/shader/ch1_primary.glsl");

    // create triangle vertices
    float vert[] = {
            //pos          //color             // texCoords
            0.99f, 0.99f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
            0.99f, -0.99f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
            -0.99f, -0.99f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
            -0.99f, 0.99f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
    };
    unsigned int indi[] = {
            0, 1, 3,
            1, 2, 3
    };

    // vao
    unsigned int vao;
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

    // vbo
    unsigned int vbo;
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vert), vert, GL_STATIC_DRAW);

    // ebo
    unsigned int ebo;
    glGenBuffers(1, &ebo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indi), indi, GL_STATIC_DRAW);

    // pos
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 7 * sizeof(float), (void *) nullptr);
    glEnableVertexAttribArray(0);
    // color
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 7 * sizeof(float), (void *) (2 * sizeof(float)));
    glEnableVertexAttribArray(1);
    // texCoords
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 7 * sizeof(float), (void *) (5 * sizeof(float)));
    glEnableVertexAttribArray(2);

    // pbr texture
    int w = winSize.w, h = winSize.h;
    std::string scene_name = "default_scene";
    unsigned int pbr_texture;
    glGenTextures(1, &pbr_texture);
    glBindTexture(GL_TEXTURE_2D, pbr_texture);
    // set texture wrapping parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    // set texture filtering parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // load texture
    kp::IntegrateState state;
    auto pbrImg = pbr(scene_name, w, h, state);
    int ch = (int) pbrImg->channel;
    if (nullptr != pbrImg) {
        if (ch == 3)
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, pbrImg->data.get());
        else if (ch == 4)
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pbrImg->data.get());
        else if (ch == 1)
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, pbrImg->data.get());
        // gen mipmap
        glGenerateMipmap(GL_TEXTURE_2D);
    } else {
        kgl::Log::ErrorCore("Failed to load texture2.");
    }

    shader->Use();
    shader->SetSampler2D("tex0", 0);

    // render init
//    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
//    glEnable(GL_DEPTH_TEST);
//    glEnable(GL_BLEND);
//    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    while (isRunning) {
        win->Clear();

        if (nullptr != pbrImg) {
            if (ch == 3)
                glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, pbrImg->data.get());
            else if (ch == 4)
                glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pbrImg->data.get());
            else if (ch == 1)
                glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, pbrImg->data.get());
            // gen mipmap
            glGenerateMipmap(GL_TEXTURE_2D);
        } else {
            kgl::Log::ErrorCore("Failed to load texture2.");
            break;
        }

        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, pbr_texture);

        shader->Use();
        glBindVertexArray(vao);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (void *) nullptr);

        win->Display();
    }

    glDeleteVertexArrays(1, &vao);
    glDeleteBuffers(1, &vbo);
    glDeleteBuffers(1, &ebo);

    glfwTerminate();

    stbi_flip_vertically_on_write(true);
    auto out_path = GetFileName(scene_name, ".jpg");
    if (!stbi_write_jpg(out_path.c_str(), w, h, 3, pbrImg->data.get(), 0)) {
        kgl::Log::Error("Image '{0}' output failed", out_path);
    }
    return 0;
}

kp::Ref<kp::ImageFilm> pbr(const std::string &scene_name, size_t w, size_t h, kp::IntegrateState &state) {
    int spp = 0;
    kgl::Log::Info("SET SSP(1~1000):");
    std::cin >> spp;
    spp = std::clamp(spp, 1, 1000);
    // Init scene
    auto film = kp::MakeRef<kp::ImageFilm>(w, h);
    float aspect = (float) film->size.w / film->size.h;
    auto camera = kp::MakeRef<kp::PerspectiveCamera>(
            kp::PerspectiveCamera(kp::Mat4::lookAt({0.0f, 0.0f, 7.5f}, {0.0f, 0.0f, 2.0f}),
                                  {{-1, -1.0f / aspect},
                                   {1,  1.0f / aspect}}, 0.0f, 0.0f, 0.0f, 1.0f, 65, *film));
    auto prims = kp::MakeRef<kp::ListAccel>(kp::ListAccel());

    auto trans1 = kp::Transform::Create({0.0f, 0.0f, -4.0f}, {8.0f, 8.0f, 8.0f}, {1.0f, 0.0f, 0.0f},
                                        kp::DegreesToRadians(90.0f));
    auto trans2 = kp::Transform::Create({-2.0f, -4.0f, -3.5f}, {2.0f, 2.0f, 2.0f}, {0.0f, 1.0f, 0.0f},
                                        kp::DegreesToRadians(20.0f));
    auto trans3 = kp::Transform::Create({2.0f, -4.0f, -3.5f}, {2.0f, 2.0f, 2.0f}, {0.0f, 1.0f, 0.0f},
                                        kp::DegreesToRadians(-20.0f));
    auto trans4 = kp::Transform::Create({-2.0f, -2.0f, -5.0f}, {2.0f, 6.0f, 2.0f}, {0.0f, 1.0f, 0.0f},
                                        kp::DegreesToRadians(25.0f));
    auto trans5 = kp::Transform::Create({2.0f, -2.0f, -5.0f}, {2.0f, 6.0f, 2.0f}, {0.0f, 1.0f, 0.0f},
                                        kp::DegreesToRadians(-25.0f));
    auto trans6 = kp::Transform::Create({0.0f, 2.7f, -4.5f}, {0.3f, 0.3f, 0.3f});
    auto trans7_top = kp::Transform::Create({-0.0f, 3.7f, -1.0f}, {1.0f, 1.0f, 1.0f});
    auto trans7_left = kp::Transform::Create({-3.7f, 3.5f, -1.0f}, {1.0f, 1.0f, 1.0f});
    auto trans7_right = kp::Transform::Create({3.7f, 3.0f, -1.5f}, {1.0f, 1.0f, 1.0f});
    ObjLoader conell_box_obj("../kgl-data/model/conellbox.obj");
    ObjLoader mari_obj("../kgl-data/model/mari/Mari.obj");
    ObjLoader cube_obj("../kgl-data/model/testcube.obj");
    auto conell = conell_box_obj.get(trans1);
    auto maril = mari_obj.get(trans2);
    auto marir = mari_obj.get(trans3);
    auto cubel = cube_obj.get(trans4);
    auto cuber = cube_obj.get(trans5);
    kp::Ref<kp::MatteMaterial> sphere_mat;
    SHARED_ASSET_TRY_GET("sphere_mat", kp::MakeRef<kp::MatteMaterial>(
            kp::MakeRef<kp::ConstantTexture>(kp::Spectrum(0.98f, 0.45f, 0.6f))), sphere_mat)
    auto sphere1_geo = kp::MakeRef<kp::Sphere>(trans6, trans6->inverseRef(), false);
    auto sphere1 = kp::MakeRef<kp::GeometryPrimitive>(
            kp::GeometryPrimitive(sphere_mat, sphere1_geo,
                                  kp::MakeRef<kp::DiffuseLight>(trans6, 3.0f * kp::Spectrum(1.0f, 1.0f, 1.0f),
                                                                sphere1_geo, true)));
    prims->push_back(conell);
//    prims->push_back(maril);
//    prims->push_back(marir);
//    prims->push_back(cubel);
//    prims->push_back(cuber);
    prims->push_back(sphere1);
    // lights
    std::vector<kp::Ref<kp::Light>> lights;
    lights.push_back(sphere1->getAreaLight());
//    lights.push_back(kp::MakeRef<kp::PointLight>(trans7_top, 2.0f * kp::Spectrum(1.0f, 1.0f, 1.0f)));
//    lights.push_back(kp::MakeRef<kp::PointLight>(trans7_left, 2.0f * kp::Spectrum(1.0f, 1.0f, 1.0f)));
//    lights.push_back(kp::MakeRef<kp::PointLight>(trans7_right, 2.0f * kp::Spectrum(1.0f, 1.0f, 1.0f)));
//    lights.push_back(kp::MakeRef<kp::SimpleSky>(kp::Spectrum(1.0f, 1.0f, 1.0f), kp::Spectrum(0.5f, 0.7f, 1.0f)));

    // accel
    auto bvh = kp::MakeRef<kp::BVHAccel>(kp::BVHAccel(prims->primitives, 8, kp::BVHAccel::SplitMethod::Middle));
    auto scene = kp::MakeRef<kp::Scene>(kp::Scene(scene_name, camera, bvh, lights));

    // Init tracer
    auto tracer = kp::singleton<kp::MultiTracer>::getInstance(kp::MultiTracer(kgl::get_core_numbers() - 2));
    auto sampler = kp::MakeRef<kp::PixelSampler>(spp);
    tracer->render(scene, sampler, film);

    state = tracer->state;

    return film;
}

std::string GetFileName(const std::string &name, const std::string &extend) {
    char timeCh[64] = {'\0'};
    time_t rawTime;
    std::time(&rawTime);
    std::strftime(timeCh, 64, "%Y.%m.%d-%H.%M.%S", std::localtime(&rawTime));
    std::string timeStr(timeCh);
    std::string appType;

    return "ImgOut/" + timeStr + "_" + appType + "_" + name + extend;
}